#include <p33Fxxxx.h>
#include <string.h>
#include "zerdlib.h"
#include "algos/lists/ByteFIFO.h"
#include "UART.h"

ByteFIFO U1RX_buffer, U1TX_buffer;
U16 U1RX_almostFullAlert, U1TX_almostFullAlert;
U8 U1_intProtect;

UART_ERROR UART1_init(U8 txPin, U8 rxPin, U32 baudrate, U16 txBufferSize, U16 rxBufferSize, U8 txIntPriority, U8 rxIntPriority, U8 intProtect) {
    // Initialisation des buffers

    U1TX_almostFullAlert = txBufferSize*9/10;
    U1TX_buffer = ByteFIFO_new(txBufferSize);
    if (U1TX_buffer == null) {
        return UART_ERROR_OUTOFMEMORY;
    }

    U1RX_almostFullAlert = rxBufferSize*9/10;
    if(rxBufferSize>0) {
        U1RX_buffer = ByteFIFO_new(rxBufferSize);
        if (U1RX_buffer == null) {
            ByteFIFO_free(U1TX_buffer);
            return UART_ERROR_OUTOFMEMORY;
        }
    } else {
        U1RX_buffer=null;
    }

    // Mappage des ports
    PPSInput(U1RX, rxPin);
    PPSOutput(U1TX, txPin);

    // Réglage de la vitesse de communication
    U1MODE = 0x0008; // mode rapide (si =0, il faut remplacer le /4 par /16) convient pour des baudrates jusqu'à plusieurs Mb
    U1BRG = ((FCY / baudrate) / 4) - 1;
    U1STA = 0;

    // Initialisation des interruptions

    if(intProtect>txIntPriority && intProtect>rxIntPriority) {
        U1_intProtect = intProtect;
    } else {
        U1_intProtect =   txIntPriority>rxIntPriority ? txIntPriority : rxIntPriority;
    }

    _U1TXIF = 0;
    _U1TXIP = txIntPriority;
    _U1TXIE = 1;

    U1STAbits.URXISEL = 0;
    _U1RXIF = 0;
    _U1RXIP = rxIntPriority;
    _U1RXIE = rxIntPriority>0?1:0;

    // Démarrage du module
    U1MODEbits.UARTEN = 1;
    U1STAbits.UTXEN = 1;

    return UART_ERROR_OK;
}

UART_ERROR UART1_send(const void * tab, U16 size) {
    U8 ret;
    INTERRUPT_PROTECT(U1_intProtect, {
        ret = ByteFIFO_pushBlock(U1TX_buffer, size, tab);    
    });
    
    _U1TXIF = 1;
    return ret ? UART_ERROR_OK : UART_ERROR_BUFFEROVERFLOW;
}

UART_ERROR UART1_sendStr(const char* str) {
    return UART1_send(str, strlen(str));
}

UART_ERROR UART1_read(void * tab, U16 size) {
    U8 ret;
    INTERRUPT_PROTECT(U1_intProtect, {
        ret = ByteFIFO_popBlock(U1RX_buffer, size, tab);
    });

    return ret ? UART_ERROR_OK : UART_ERROR_NOTENOUGHTDATA;
}

void UART1_onU1TXInterrupt(void) {
    if (_U1TXIF) {
        INTERRUPT_PROTECT(U1_intProtect, {
            while (!U1STAbits.UTXBF && !ByteFIFO_isEmpty(U1TX_buffer)) {
                U1TXREG = ByteFIFO_pop(U1TX_buffer);
            }
        });
        _U1TXIF = 0;
    }
}

UART_ERROR UART1_onU1RXInterrupt() {
    U8 ret = UART_ERROR_OK;
    if(_U1RXIF) {
        _U1RXIF=0;
        while(U1STAbits.URXDA && ret == UART_ERROR_OK) {
            U8 value = U1RXREG;

            INTERRUPT_PROTECT(U1_intProtect, {
                if(ByteFIFO_isFull(U1RX_buffer)) {
                    ret = UART_ERROR_BUFFEROVERFLOW;
                } else {
                    ByteFIFO_push(U1RX_buffer,value);
                    if(ByteFIFO_getDataSize(U1RX_buffer)>U1RX_almostFullAlert) {
                        ret = UART_ERROR_BUFFERALMOSTFULL;
                    } else {
                        ret = UART_ERROR_OK;
                    }
                }
            });
        }
        return ret;
    }
}

